package mx.unam.acatlan.poo.tournament;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import mx.unam.acatlan.poo.rps.Enfrentamiento;
import mx.unam.acatlan.poo.rps.Movement;
import mx.unam.acatlan.poo.rps.RPSRecord;
import mx.unam.acatlan.poo.rps.player.Agent;
import mx.unam.acatlan.poo.rps.player.RandomAgent;
import mx.unam.acatlan.poo.rps.player.ConstantAgent;

/**
 * Realiza un torneo de Piedra, Papel, o Tijeras
 * 
 * <p style="text-align: justify">
 * Realiza un tornero de Piedra, Papel o Tijeras entre un conjunto de al menos
 * dos jugadores. Los jugadores (que en adelante llamaremos <b>agentes</b>) son
 * clases concretas que heredan de {@link Agent}.
 * </p>
 * <h3>Juegos</h3>
 * <p style="text-align: justify">
 * El resultado de un juego se expresa por una puntiacion numérica que se asigna
 * a cada cada agente participante, de acuerdo con las siguientes reglas:
 * <ul>
 * <li>
 * Si el agente venció utilizando {@link Movement#PIEDRA},
 * {@link Movement#TIJERAS}, o {@link Movement#PAPEL}, obtiene 1 punto</li>
 * <li>
 * Si el agente empató utilizando {@link Movement#PIEDRA},
 * {@link Movement#TIJERAS}, o {@link Movement#PAPEL}, obtiene 0 puntos.</li>
 * <li>
 * Si el agente utilizó {@link Movement#NULO}, obtiene -1 puntos.</li>
 * </ul>
 * </p>
 * <p style="text-align: justify">
 * Supongamos que los agentes <code>j1</code>, y <code>j2</code> participan en
 * un {@link RPSRecord}. Para determinar las puntuaciones finales de cada agente se
 * realizan los siguientes pasos:
 * <ol>
 * <li>
 * Se ejecuta {@link Agent#play(Enfrentamiento)} en el agente <code>j1</code>.
 * {@link Enfrentamiento#getJuegos()} regresa los juegos en los que ha participado el
 * agente <code>j2</code>.</li>
 * <li>
 * Se ejecuta {@link Agent#play(Enfrentamiento)} en el agente <code>j2</code>.
 * {@link Enfrentamiento#getJuegos()} regresa los juegos en los que ha participado el
 * agente <code>j1</code>.</li>
 * <li>
 * En caso de que algún agente no utilice
 * {@link Enfrentamiento#setMovimiento(Movement)}, se establecerá
 * {@link Movement#NULO} como movimiento por default.</li>
 * <li>
 * Se determina el ganador del juego de acuerdo con el ciclo "
 * {@link Movement#PIEDRA} vence a {@link Movement#TIJERAS} vence a
 * {@link Movement#PAPEL} vence a {@link Movement#PIEDRA}". Todos los
 * movements vencen a {@link Movement#NULO}.</li>
 * <li>
 * Se determina un empate si ambos agentes utilizan el mismo movimiento.</li>
 * </ol>
 * </p>
 * <h3>Enfrentamientos</h3>
 * <p style="text-align: justify">
 * En un enfrentamiento, dos agentes participan en juegos consecutivos hasta que
 * una de las siguientes condiciones se cumple:
 * <ul>
 * <li>
 * Alguno de los agentes acumula 2 puntos.</li>
 * <li>
 * Los agentes empatan tres veces.</li>
 * <li>
 * Los agentes participan en más de cincuenta juegos consecutivos.</li>
 * </ul>
 * </p>
 * <h3>Fase preliminar del Tournament</h3>
 * <p style="text-align: justify">
 * En orden aleatorio, todos los agentes tendrán un enfrentamiento en contra de
 * {@link ConstantAgent} y {@link RandomAgent}. Esta fase tiene como
 * objetivo obtener información sobre el comportamiento de los participantes
 * para ser incluida en {@link Enfrentamiento} durante el resto de los enfrentamientos, y
 * obtener una puntuación base que será utilizada durante el torneo, como se
 * menciona en el siguiente punto.
 * </p>
 * <h3>Tournament</h3>
 * <p style="text-align:justify">
 * El tornero se realiza por eliminación inmediata basada en enfrentamientos. A
 * cada agente se le asigna una puntuación global y una puntuación local en cada
 * etapa de eliminación. La puntuación local de un agente, es la que obtiene
 * después de participar en el enfrentamiento correspondiente a la etapa de
 * eliminación actual. La puntuación global se calcula sumando las puntuaciones
 * locales de todas las etapas de eliminación anteriores incluyendo la fase
 * preliminar, y la etapa de eliminación actual. </br></br> El avance de un
 * agente en cada etapa se determina de acuerdo con las siguientes reglas:
 * <ul>
 * <li>
 * Un agente avanza a la siguiente etapa si obtuvo una puntuación local mayor a
 * la de su contrincante en la etapa de eliminación actual.</li>
 * <li>
 * En caso de empate, un agente avanza si tiene una puntuación global mayor a la
 * de su contrincante.</li>
 * <li>
 * Si el empate no puede romperse con las reglas anteriores, los agente se
 * enfrentarán en muerte súbita, hasta que alguno de ellos gane. Si después de
 * 50 juegos no se rompe el empate, se elegirá al ganador aleatoriamente.</li>
 * </ul>
 * </p>
 * <p style="text-align:justify">
 * Finalmente, si el número inicial de agentes es impar, después de la fase
 * preliminar se eliminanará el agente con menor puntuación. Las parejas se
 * formarán aleatoriamente para realizar la primer etapa de eliminación.
 * </p>
 * 
 * @author Carlos Alegria Galicia
 */
public final class Tournament {
	
	/**
	 *
	 */
	public static final String LOGGER_LEVEL = "logging.level";
	
	/**
	 *
	 */
	public static final String JUGADORES_MINIMOS = "torneo.jugadores.minimos";
	
	/**
	 *
	 */
	public static final String JUGADORES_MINIMOS_DEFAULT = "2";
	
	/**
	 *
	 */
	private static final String JUGADORES_MINIMOS_ERROR_NO_ = "La propiedad "
			+ Tournament.JUGADORES_MINIMOS + " requiere un valor de tipo entero.";
	
	/**
	 *
	 */
	private static final String JUGADORES_MINIMOS_ERROR_2 =  "El número de "
			+ "jugadores registrados es menor que el indicado en la propiedad "
			+ Tournament.JUGADORES_MINIMOS + ".";
	/**
	 *
	 */
	public static final String JUGADORES = "torneo.jugadores";
	
	/**
	 *
	 */
	public static final String JUGADORES_ERROR_1 = "No pudo encontrarse la "
			+ "propiedad " + Tournament.JUGADORES + ".";
	
	/**
	 *
	 */
	private static final Logger logger = Logger.getLogger(Tournament.class.getCanonicalName());
	
	/**
	 *
	 */
	private final Properties properties;
	
	/**
	 *
	 */
	private Player jugadores[];
	
	/**
	 * 
	 */
	private Enfrentamiento enfrentamiento;
	
	
	/**
	 * @param config 
	 * @throws IOException
	 * @throws FileNotFoundException 
	 * @throws TournamentConfigurationException 
	 */
	public Tournament(String config) throws FileNotFoundException, IOException,
			TournamentConfigurationException {
		
		this.properties = new Properties();
		this.properties.load(new FileReader(config));
		
		//
		// verificando propiedad JUGADORES
		//
		
		if (properties.getProperty(Tournament.JUGADORES) == null) {
			throw new TournamentConfigurationException(Tournament.JUGADORES_ERROR_1);
		}
		
		Agent agent;
		Vector<Agent> vectorDeJugadores = new Vector<>();
		for (String clase : properties.getProperty(Tournament.JUGADORES).split(",")) {
			try {
				agent = (Agent) Class.forName(clase.trim()).newInstance(); 
				vectorDeJugadores.add(agent);
				logger.info("El jugador '" + agent + "' de tipo " +
						agent.getClass() + " ha sido registrado.");
			} catch (Exception e) {
				logger.log(Level.WARNING,
						"No se pudo instanciar el jugador de la clase '" + clase + "'",
						e);
			}
		}
		logger.info(vectorDeJugadores.size() + " jugadores registrados");
		
		//
		// verificando propiedad JUGADORES_MINIMOS 
		//
		
		int jugadoresMinimos;
		try {
			jugadoresMinimos = Integer.parseInt(
								properties.getProperty(
										Tournament.JUGADORES_MINIMOS,
										Tournament.JUGADORES_MINIMOS_DEFAULT));
		} catch (NumberFormatException e) {
			throw new TournamentConfigurationException(
					Tournament.JUGADORES_MINIMOS_ERROR_NO_);
		}
		
		if (vectorDeJugadores.size() < jugadoresMinimos) {
			logger.severe(Tournament.JUGADORES_MINIMOS_ERROR_2);
			throw new TournamentConfigurationException(
					Tournament.JUGADORES_MINIMOS_ERROR_2);
		}
		
		this.jugadores = new Agent[vectorDeJugadores.size()];
		vectorDeJugadores.toArray(this.jugadores);
		this.enfrentamiento = new Enfrentamiento();
	}

	/**
	 * 
	 */
	public void ejecutar() {
		Agent retadores[] = { new RandomAgent(), new ConstantAgent() };
		
		// creamos juegos para los retadores
		//
		for (int i = 0; i < 50; i++) {
			this.enfrentamiento.enfrentar(retadores[0], retadores[1]);
		}
		
		// enfrentamos los retadores con los jugadores registrados
		//
		
		
		//
		//
		for (int i = 0; i < jugadores.length - 1; i++) {
			for (int j = i; j < jugadores.length; j++) {
				this.enfrentamiento.enfrentar(jugadores[i], jugadores[j]);
			}
		}
	}
}